import {BackSide, FrontSide, CubeUVReflectionMapping} from '../../constants.js';
import {BoxBufferGeometry} from '../../geometries/BoxGeometry.js';
import {PlaneBufferGeometry} from '../../geometries/PlaneGeometry.js';
import {ShaderMaterial} from '../../materials/ShaderMaterial.js';
import {Color} from '../../math/Color.js';
import {Mesh} from '../../objects/Mesh.js';
import {ShaderLib} from '../shaders/ShaderLib.js';
import {cloneUniforms} from '../shaders/UniformsUtils.js';

function WebGLBackground(renderer, cubemaps, state, objects, premultipliedAlpha) {

  const clearColor = new Color(0x000000);
  let clearAlpha = 0;

  let planeMesh;
  let boxMesh;

  let currentBackground = null;
  let currentBackgroundVersion = 0;
  let currentTonemapping = null;

  function render(renderList, scene, camera, forceClear) {

    let background = scene.isScene === true ? scene.background : null;

    if (background && background.isTexture) {

      background = cubemaps.get(background);

    }

    // Ignore background in AR
    // TODO: Reconsider this.

    const xr = renderer.xr;
    const session = xr.getSession && xr.getSession();

    if (session && session.environmentBlendMode === 'additive') {

      background = null;

    }

    if (background === null) {

      setClear(clearColor, clearAlpha);

    } else if (background && background.isColor) {

      setClear(background, 1);
      forceClear = true;

    }

    if (renderer.autoClear || forceClear) {

      renderer.clear(renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil);

    }

    if (background && (background.isCubeTexture || background.isWebGLCubeRenderTarget || background.isWebGLCubeRenderTargetTexture || background.mapping === CubeUVReflectionMapping)) {

      if (boxMesh === undefined) {

        boxMesh = new Mesh(
          new BoxBufferGeometry(1, 1, 1),
          new ShaderMaterial({
            name: 'BackgroundCubeMaterial',
            uniforms: cloneUniforms(ShaderLib.cube.uniforms),
            vertexShader: ShaderLib.cube.vertexShader,
            fragmentShader: ShaderLib.cube.fragmentShader,
            side: BackSide,
            depthTest: false,
            depthWrite: false,
            fog: false
          })
        );

        boxMesh.geometry.deleteAttribute('normal');
        boxMesh.geometry.deleteAttribute('uv');

        boxMesh.onBeforeRender = function (renderer, scene, camera) {

          this.matrixWorld.copyPosition(camera.matrixWorld);

        };

        // enable code injection for non-built-in material
        Object.defineProperty(boxMesh.material, 'envMap', {

          get: function () {

            return this.uniforms.envMap.value;

          }

        });

        objects.update(boxMesh);

      }

      if (background.isWebGLCubeRenderTarget) {

        // TODO Deprecate

        background = background.texture;

      }

      boxMesh.material.uniforms.envMap.value = background;
      boxMesh.material.uniforms.flipEnvMap.value = background.isCubeTexture ? -1 : 1;

      if (currentBackground !== background ||
        currentBackgroundVersion !== background.version ||
        currentTonemapping !== renderer.toneMapping) {

        boxMesh.material.needsUpdate = true;

        currentBackground = background;
        currentBackgroundVersion = background.version;
        currentTonemapping = renderer.toneMapping;

      }

      // push to the pre-sorted opaque render list
      renderList.unshift(boxMesh, boxMesh.geometry, boxMesh.material, 0, 0, null);

    } else if (background && background.isTexture) {

      if (planeMesh === undefined) {

        planeMesh = new Mesh(
          new PlaneBufferGeometry(2, 2),
          new ShaderMaterial({
            name: 'BackgroundMaterial',
            uniforms: cloneUniforms(ShaderLib.background.uniforms),
            vertexShader: ShaderLib.background.vertexShader,
            fragmentShader: ShaderLib.background.fragmentShader,
            side: FrontSide,
            depthTest: false,
            depthWrite: false,
            fog: false
          })
        );

        planeMesh.geometry.deleteAttribute('normal');

        // enable code injection for non-built-in material
        Object.defineProperty(planeMesh.material, 'map', {

          get: function () {

            return this.uniforms.t2D.value;

          }

        });

        objects.update(planeMesh);

      }

      planeMesh.material.uniforms.t2D.value = background;

      if (background.matrixAutoUpdate === true) {

        background.updateMatrix();

      }

      planeMesh.material.uniforms.uvTransform.value.copy(background.matrix);

      if (currentBackground !== background ||
        currentBackgroundVersion !== background.version ||
        currentTonemapping !== renderer.toneMapping) {

        planeMesh.material.needsUpdate = true;

        currentBackground = background;
        currentBackgroundVersion = background.version;
        currentTonemapping = renderer.toneMapping;

      }


      // push to the pre-sorted opaque render list
      renderList.unshift(planeMesh, planeMesh.geometry, planeMesh.material, 0, 0, null);

    }

  }

  function setClear(color, alpha) {

    state.buffers.color.setClear(color.r, color.g, color.b, alpha, premultipliedAlpha);

  }

  return {

    getClearColor: function () {

      return clearColor;

    },
    setClearColor: function (color, alpha) {

      clearColor.set(color);
      clearAlpha = alpha !== undefined ? alpha : 1;
      setClear(clearColor, clearAlpha);

    },
    getClearAlpha: function () {

      return clearAlpha;

    },
    setClearAlpha: function (alpha) {

      clearAlpha = alpha;
      setClear(clearColor, clearAlpha);

    },
    render: render

  };

}


export {WebGLBackground};
